using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using ZoneFiveSoftware.Common.Data.Fitness;
using FilteredStatistics.Data;
using FilteredStatistics.View;
using FilteredStatistics.Common.Controller;

namespace FilteredStatistics.Controller
{
    class TemplateManager : IPluginSerializable
    {
        private TemplateManager()
        {
            PluginMain.LogbookChanged += new PluginMain.LogbookChangedEventHandler(OnLogbookChanged);
            FilterCriteriaController.Instance.AvailableCriteriasChanged += new PropertyChangedEventHandler(OnAvailableCriteriasChanged);

            LastUsedFilter.FilterChanged += new FilterList.FilterChangedEventHandler(OnLastUsedFilterChanged);
            LastUsedFilter.FilterListChanged += new FilterList.FilterListChangedEventHandler(OnLastUsedFilterListChanged);
        }

        public static TemplateManager Instance
        {
            get { return m_Instance; }
        }

#region IPluginSerializable members

        public override void Serialize(Stream stream)
        {
            SerializableInt32Range templateCount = new SerializableInt32Range(Templates.Count);

            templateCount.Serialize(stream);

            // Two step serialization to ease deserialization of templates critera
            //  We start by serializing all names so we can create the templates at the
            //  beginning of deserialization so there's no need for placeholders for templates
            //  that might not yet exist because they are further down the road.  This way
            //  we can at least have the pointer to the template named zone even if it wasn't
            //  deserialized yet!

            // First serialize all names
            foreach (Template currentTemplate in Templates)
            {
                SerializableString templateName = new SerializableString(currentTemplate.Name);

                templateName.Serialize(stream);
            }

            // Then serialize the template data
            foreach (Template currentTemplate in Templates)
            {
                currentTemplate.Serialize(stream);
            }

            // Now serialize the last used filter list
            LastUsedFilter.Serialize(stream);
        }

#endregion

#region Deserialization methods

        public void Deserialize_V1(Stream stream, DataVersion version)
        {
            ClearAllTemplates();

            SerializableInt32Range templateCount = new SerializableInt32Range(0);

            templateCount.Deserialize(stream, version);

            // Deserialize all names, and create a matching template that we can later deserialize
            for (int i = 0; i < templateCount; ++i)
            {
                SerializableString templateName = new SerializableString();

                templateName.Deserialize(stream, version);

                Template newTemplate = new Template(templateName, new FilterList(null));

                AddTemplateInternal(newTemplate);
            }

            TriggerPropertyChangedEvent("Templates");

            // Deserialize the whole data
            foreach (Template currentTemplate in Templates)
            {
                currentTemplate.Deserialize(stream, version);
            }

            ReorderTemplatesAlphabetically();

            TriggerPropertyChangedEvent("Templates");
        }

        public void Deserialize_V2(Stream stream, DataVersion version)
        {
            Deserialize_V1(stream, version);

            LastUsedFilter.Deserialize(stream, version);

            TriggerPropertyChangedEvent("LastUsedFilter");
        }

#endregion

        protected void TriggerPropertyChangedEvent(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void TriggerPropertyChangedEvent(object sender, string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
            }
        }

        private void OnTemplatePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ShowOnlyInSettings")
            {
                // Make sure our settings only template isn't selected
                Template modifiedTemplate = sender as Template;

                foreach (Filter filter in LastUsedFilter)
                {
                    if (filter.CriteriaFacade is TemplateFilterCriteria)
                    {
                        foreach (TemplateNamedZone namedZone in filter.CriteriaFacade.ConcreteNamedZones)
                        {
                            if (namedZone.Template == modifiedTemplate)
                            {
                                if (namedZone.Template.ShowOnlyInSettings &&
                                    filter.IsZoneSelected(namedZone))
                                {
                                    filter.RemoveSelectedZone(namedZone);
                                }

                                break;
                            }
                        }
                    }
                }
            }

            TriggerPropertyChangedEvent(sender, "Template");

            if (e.PropertyName != "ValidTimes")
            {
                SerializeTemplatesToLogbook();
            }
        }

        void OnLastUsedFilterChanged(Filter filter, PropertyChangedEventArgs changedProperty)
        {
            if (changedProperty.PropertyName != "ValidTimes")
            {
                SerializeTemplatesToLogbook();
            }
        }

        void OnLastUsedFilterListChanged(FilterList filterList, PropertyChangedEventArgs changedProperty)
        {
            if (changedProperty.PropertyName != "ValidTimes" && changedProperty.PropertyName != "NewPlaceholderFilter")
            {
                SerializeTemplatesToLogbook();
            }
        }

        private void OnLogbookChanged(object sender, ILogbook oldLogbook, ILogbook newLogbook)
        {
            ClearAllTemplates();

            FilterCriteriaController.Instance.ReloadAvailableCriterias(newLogbook);

            Utils.DeserializeTemplatesFromLogbook(newLogbook);
        }

        private void OnAvailableCriteriasChanged(object sender, PropertyChangedEventArgs e)
        {
            CheckTemplatesConsistency();
        }

        public void AddTemplate(Template newTemplate)
        {
            AddTemplateInternal(newTemplate);

            ReorderTemplatesAlphabetically();

            TriggerPropertyChangedEvent("Templates");
        }

        private void AddTemplateInternal(Template newTemplate)
        {
            Template template = GetTemplate(newTemplate.Name);

            if (template == null)
            {
                m_Templates.Add(newTemplate);

                newTemplate.PropertyChanged += new PropertyChangedEventHandler(OnTemplatePropertyChanged);

                SerializeTemplatesToLogbook();
            }
        }

        private void ClearAllTemplates()
        {
            while (m_Templates.Count > 0)
            {
                m_Templates[0].PropertyChanged -= new PropertyChangedEventHandler(OnTemplatePropertyChanged);

                m_Templates.RemoveAt(0);
            }

            m_LastUsedFilter.Clear();
        }

        public void RemoveTemplate(String templateName)
        {
            Template template = GetTemplate(templateName);

            if (template != null)
            {
                template.PropertyChanged -= new PropertyChangedEventHandler(OnTemplatePropertyChanged);

                m_Templates.Remove(template);

                TriggerPropertyChangedEvent("Templates");

                SerializeTemplatesToLogbook();
            }
        }

        public void RemoveTemplate(Template templateToRemove)
        {
            RemoveTemplate(templateToRemove.Name);
        }

        public void RenameTemplate(Template templateToRename, String newName)
        {
            Template newNameTemplate = GetTemplate(newName);
            Debug.Assert(Templates.Contains(templateToRename));
            Debug.Assert(newNameTemplate == null || newNameTemplate == templateToRename);

            templateToRename.Name = newName;

            ReorderTemplatesAlphabetically();

            TriggerPropertyChangedEvent("Templates");
        }

        public int GetTemplateCount()
        {
            return Templates.Count;
        }

        public Template GetTemplate(String name)
        {
            foreach (Template currentTemplate in Templates)
            {
                if (currentTemplate.Name == name)
                {
                    return currentTemplate;
                }
            }

            return null;
        }

        public Template GetTemplate(int index)
        {
            Debug.Assert(index >= 0 && index < Templates.Count);

            return Templates[index];
        }

        public void SerializeTemplatesToLogbook()
        {
            // Don't serialize during or with a pending deserialization
            if (!Deserializing)
            {
                // Save templates to logbook
                MemoryStream stream = new MemoryStream();

                SerializableString header = new SerializableString(Constants.DataHeaderIdV3String);
                SerializableUInt16Range version = new SerializableUInt16Range(Constants.CurrentVersion.VersionNumber);

                header.Serialize(stream);
                version.Serialize(stream);

                Serialize(stream);

                PluginMain.GetApplication().Logbook.SetExtensionData(GUIDs.PluginMain, stream.GetBuffer());
                PluginMain.GetApplication().Logbook.Modified = true;
                stream.Close();
            }
        }

        public void DeserializeTemplatesFromStream(Stream stream, DataVersion version)
        {
            if (!Deserializing)
            {
                Deserializing = true;

                Deserialize(stream, version);

                Deserializing = false;
            }
        }

        protected void ReorderTemplatesAlphabetically()
        {
            Templates.Sort(CompareTemplates);
        }

        private int CompareTemplates(Template template1, Template template2)
        {
            return template1.Name.CompareTo(template2.Name);
        }

        private void CheckTemplatesConsistency()
        {
            if (!Deserializing)
            {
                // Check templates for inconsistencies
                List<Template> inconsistentTemplates = new List<Template>();

                for (int i = 0; i < TemplateManager.Instance.GetTemplateCount(); ++i)
                {
                    Template currentTemplate = TemplateManager.Instance.GetTemplate(i);

                    if (currentTemplate.Filters.ValidateFiltersConsistency())
                    {
                        inconsistentTemplates.Add(currentTemplate);
                    }
                }

                // Check last used filter
                LastUsedFilter.ValidateFiltersConsistency();

                // Display a message notifying that templates we're updated
                if (Options.Instance.ShowTemplatesModifiedWarning &&
                    inconsistentTemplates.Count > 0)
                {
                    String message = PluginView.GetLocalizedString("ModifiedTemplateWarningText");
                    String modifiedTemplatesString = "";
                    DialogResult questionAnswer;

                    foreach (Template modifiedTemplate in inconsistentTemplates)
                    {
                        modifiedTemplatesString += modifiedTemplate.Name + "\n";
                    }

                    message = String.Format(message, modifiedTemplatesString);
                    questionAnswer = MessageBox.Show(message,
                                                     PluginView.GetLocalizedString("FilteredStatisticsWarningText"),
                                                     MessageBoxButtons.YesNo,
                                                     MessageBoxIcon.Question);

                    if (questionAnswer == DialogResult.No)
                    {
                        Options.Instance.ShowTemplatesModifiedWarning = false;
                    }
                }
            }
        }

        public FilterListWithCache LastUsedFilter
        {
            get
            {
                return m_LastUsedFilter;
            }
        }

        protected List<Template> Templates
        {
            get
            {
                return m_Templates;
            }
        }

        protected bool Deserializing
        {
            get { return m_Deserializing; }
            set { m_Deserializing = value; }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private static TemplateManager m_Instance = new TemplateManager();
        private List<Template> m_Templates = new List<Template>();
        private FilterListWithCache m_LastUsedFilter = new FilterListWithCache(null);
        private bool m_Deserializing = false;
    }
}
